/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.wdullaer.materialdatetimepicker.date;

import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.Component.ClickedListener;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.LayoutScatter;
import ohos.agp.components.Text;
import ohos.agp.text.Font;
import ohos.agp.utils.Color;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.utils.RectFloat;
import ohos.agp.utils.TextAlignment;
import ohos.agp.window.service.WindowManager;
import ohos.app.Context;
import ohos.global.resource.ResourceManager;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

import com.wdullaer.materialdatetimepicker.HapticFeedbackController;
import com.wdullaer.materialdatetimepicker.JalaliCalendar;
import com.wdullaer.materialdatetimepicker.ResourceTable;
import com.wdullaer.materialdatetimepicker.Utils;
import com.wdullaer.materialdatetimepicker.common.DialogBase;
import com.wdullaer.materialdatetimepicker.common.DialogInterface;
import com.wdullaer.materialdatetimepicker.common.DialogInterface.OnBackgroundListener;
import com.wdullaer.materialdatetimepicker.common.ResourceUtils;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.Locale;
import java.util.TimeZone;

/**
 * Dialog allowing users to select a date.
 */
public class DatePickerDialog extends DialogBase implements
    ClickedListener, DatePickerController, OnBackgroundListener {

    private static final String TAG = "MonthFragment";
    private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 1234567, TAG);
    private Font font = null;

    public enum Version {
        VERSION_1,
        VERSION_2
    }

    public enum ScrollOrientation {
        HORIZONTAL,
        VERTICAL
    }

    public enum Type {
        GREGORIAN,
        JALALI
    }

    private static final int UNINITIALIZED = -1;
    private static final int MONTH_AND_DAY_VIEW = 0;
    private static final int YEAR_VIEW = 1;

    private static final String KEY_SELECTED_YEAR = "year";
    private static final String KEY_SELECTED_MONTH = "month";
    private static final String KEY_SELECTED_DAY = "day";
    private static final String KEY_LIST_POSITION = "list_position";
    private static final String KEY_WEEK_START = "week_start";
    private static final String KEY_CURRENT_VIEW = "current_view";
    private static final String KEY_LIST_POSITION_OFFSET = "list_position_offset";
    private static final String KEY_HIGHLIGHTED_DAYS = "highlighted_days";
    private static final String KEY_THEME_DARK = "theme_dark";
    private static final String KEY_THEME_DARK_CHANGED = "theme_dark_changed";
    private static final String KEY_ACCENT = "accent";
    private static final String KEY_VIBRATE = "vibrate";
    private static final String KEY_DISMISS = "dismiss";
    private static final String KEY_AUTO_DISMISS = "auto_dismiss";
    private static final String KEY_DEFAULT_VIEW = "default_view";
    private static final String KEY_TITLE = "title";
    private static final String KEY_OK_RESID = "ok_resid";
    private static final String KEY_OK_STRING = "ok_string";
    private static final String KEY_OK_COLOR = "ok_color";
    private static final String KEY_CANCEL_RESID = "cancel_resid";
    private static final String KEY_CANCEL_STRING = "cancel_string";
    private static final String KEY_CANCEL_COLOR = "cancel_color";
    private static final String KEY_VERSION = "version";
    private static final String KEY_TIMEZONE = "timezone";
    private static final String KEY_DATERANGELIMITER = "daterangelimiter";
    private static final String KEY_SCROLL_ORIENTATION = "scrollorientation";
    private static final String KEY_LOCALE = "locale";

    private static final int ANIMATION_DURATION = 300;
    private static final int ANIMATION_DELAY = 500;

    private SimpleDateFormat YEAR_FORMAT = new SimpleDateFormat("yyyy", Locale.getDefault());
    private SimpleDateFormat MONTH_FORMAT = new SimpleDateFormat("MMM", Locale.getDefault());
    private SimpleDateFormat DAY_FORMAT = new SimpleDateFormat("dd", Locale.getDefault());
    private SimpleDateFormat VERSION_2_FORMAT;

    private Calendar mCalendar = Utils.trimToMidnight(Calendar.getInstance(getTimeZone()));
    private OnDateSetListener mCallBack;
    private HashSet<OnDateChangedListener> mListeners = new HashSet<>();

    private AccessibleDateAnimator mAnimator;

    private Text mDatePickerHeaderView;
    private DirectionalLayout mMonthAndDayView;
    private Text mSelectedMonthTextView;
    private Text mSelectedDayTextView;
    private Text mYearView;
    private DayPickerGroup mDayPickerView;
    private YearPickerView mYearPickerView;

    private int mCurrentView = UNINITIALIZED;

    private int mWeekStart; // = mCalendar.getFirstDayOfWeek();
    private String mTitle;
    private HashSet<Calendar> highlightedDays = new HashSet<>();
    private boolean mThemeDark = false;
    private boolean mThemeDarkChanged = false;
    private Integer mAccentColor = null;
    private boolean mVibrate = true;
    private boolean mDismissOnPause = false;
    private boolean mAutoDismiss = false;
    private int mDefaultView = MONTH_AND_DAY_VIEW;
    private int mOkResid = ResourceTable.String_mdtp_ok;
    private String mOkString;
    private Integer mOkColor = null;
    private int mCancelResid = ResourceTable.String_mdtp_cancel;
    private String mCancelString;
    private Integer mCancelColor = null;
    private Version mVersion;
    private ScrollOrientation mScrollOrientation;
    private TimeZone mTimezone;
    private Locale mLocale = Locale.getDefault();
    private DefaultDateRangeLimiter mDefaultLimiter = new DefaultDateRangeLimiter();
    private DateRangeLimiter mDateRangeLimiter = mDefaultLimiter;

    private Type calendarType = Type.GREGORIAN;

    private HapticFeedbackController mHapticFeedbackController;

    private boolean mDelayAnimation = true;

    // Accessibility strings.
    private String mDayPickerDescription;
    private String mSelectDay;
    private String mYearPickerDescription;
    private String mSelectYear;

//    private Context mContext;

    /**
     * The callback used to indicate the user is done filling in the date.
     */
    public interface OnDateSetListener {

        /**
         * 日期设置
         *
         * @param dialog The view associated with this listener.
         * @param year The year that was set.
         * @param monthOfYear The month that was set (0-11) for compatibility
         * with {@link Calendar}.
         * @param dayOfMonth The day of the month that was set.
         */
        void onDateSet(DatePickerDialog dialog, int year, int monthOfYear, int dayOfMonth);
    }

    /**
     * The callback used to notify other date picker components of a change in selected date.
     */
    protected interface OnDateChangedListener {
        void onDateChanged();
    }


    public DatePickerDialog(Context context) {
        super(context);
//        mContext = context;
        // Empty constructor required for dialog fragment.
//        context.setPattern(ResourceTable.Pattern_base);
    }

    /**
     * 新实例
     * Create a new DatePickerDialog instance with a specific initial selection.
     *
     * @param callBack How the parent is notified that the date is set.
     * @param year The initial year of the dialog.
     * @param monthOfYear The initial month of the dialog.
     * @param dayOfMonth The initial day of the dialog.
     * @param calendarType 日历类型
     * @param context 上下文
     * @return a new DatePickerDialog instance.
     */
    public static DatePickerDialog newInstance(Type calendarType, Context context, OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) {
        DatePickerDialog ret = new DatePickerDialog(context);
        ret.setCalendarType(calendarType);
        ret.initialize(callBack, year, monthOfYear, dayOfMonth);
        return ret;
    }

    /**
     * Create a new DatePickerDialog instance initialised to the current system date.
     *
     * @param callback How the parent is notified that the date is set.
     * @param calendarType 日历类型
     * @param context 上下文
     * @return a new DatePickerDialog instance
     */
    @SuppressWarnings({"unused", "WeakerAccess"})
    public static DatePickerDialog newInstance(Type calendarType, Context context, OnDateSetListener callback) {
        Calendar now = null;

        switch (calendarType) {
            case GREGORIAN:
                now = Calendar.getInstance();
                break;

            case JALALI:
                now = JalaliCalendar.getInstance();
                break;
        }
        return DatePickerDialog.newInstance(context, callback, now);
    }

    /**
     * Create a new DatePickerDialog instance with a specific initial selection.
     *
     * @param callback How the parent is notified that the date is set.
     * @param initialSelection A Calendar object containing the original selection of the picker.
     * (Time is ignored by trimming the Calendar to midnight in the current
     * TimeZone of the Calendar object)
     * @param context 上下文
     * @return a new DatePickerDialog instance
     */
    @SuppressWarnings({"unused", "WeakerAccess"})
    public static DatePickerDialog newInstance(Context context, OnDateSetListener callback, Calendar initialSelection) {
        DatePickerDialog ret = new DatePickerDialog(context);
        ret.initialize(callback, initialSelection);
        return ret;
    }

    public void initialize(OnDateSetListener callBack, Calendar initialSelection) {
        mCallBack = callBack;
        switch (calendarType) {
            case GREGORIAN:
                mCalendar = Utils.trimToMidnight((Calendar) initialSelection.clone());
                mDefaultLimiter.setYearRange(1900, 2100);
                break;
            case JALALI:
                mCalendar = Utils.trimToMidnight((JalaliCalendar) initialSelection.clone());
                mDefaultLimiter.setYearRange(1300, 1500);
                break;
        }


        mWeekStart = mCalendar.getFirstDayOfWeek();
        mScrollOrientation = null;
        //noinspection deprecation
        setTimeZone(mCalendar.getTimeZone());

        mVersion = Version.VERSION_2;

        HiLog.warn(LABEL, "### dpd initialize done");
    }

    public void initialize(OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) {
        Calendar cal = null;
        switch (calendarType) {
            case GREGORIAN:
                cal = Calendar.getInstance(getTimeZone());
                break;
            case JALALI:
                cal = JalaliCalendar.getInstance(getTimeZone());
                break;
        }
        if (cal != null) {
            cal.set(Calendar.YEAR, year);
            cal.set(Calendar.MONTH, monthOfYear);
            cal.set(Calendar.DAY_OF_MONTH, dayOfMonth);
            this.initialize(callBack, cal);
        }
    }

    @Override
    protected void onCreate() {
        super.onCreate();

        mCurrentView = UNINITIALIZED;

        ResourceManager res = context.getResourceManager();
        VERSION_2_FORMAT = new SimpleDateFormat(ResourceUtils.getString(res, ResourceTable.String_mdtp_date_v2_daymonthyear), mLocale);
        VERSION_2_FORMAT.setTimeZone(getTimeZone());

        mHapticFeedbackController = new HapticFeedbackController(context);

        Component view = createCompnent(LayoutScatter.getInstance(context), context);

        DirectionalLayout layout = new DirectionalLayout(context);
        layout.setLayoutConfig(new ComponentContainer.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_PARENT));
        layout.setAlignment(LayoutAlignment.CENTER);
        layout.addComponent(view);

        setContentCustomComponent(layout);
        setTransparent(true);
    }

    protected Component createCompnent(LayoutScatter scatter, Context context) {
        int listPosition = -1;
        int listPositionOffset = 0;
        int currentView = mDefaultView;
        if (mScrollOrientation == null) {
            mScrollOrientation = mVersion == Version.VERSION_1
                ? ScrollOrientation.VERTICAL
                : ScrollOrientation.HORIZONTAL;
        }

        mDefaultLimiter.setController(this);

        HiLog.warn(LABEL, "### createCompnent mVersion:" + mVersion);
        int viewRes = mVersion == Version.VERSION_1 ? ResourceTable.Layout_mdtp_date_picker_dialog_w270dp_h560dp : ResourceTable.Layout_mdtp_date_picker_dialog_v2;
        Component view = scatter.parse(viewRes, null, true);
        // All options have been set at this point: round the initial selection if necessary
        mCalendar = mDateRangeLimiter.setToNearestDate(mCalendar);

        mDatePickerHeaderView = (Text) view.findComponentById(ResourceTable.Id_mdtp_date_picker_header);
        mMonthAndDayView = (DirectionalLayout) view.findComponentById(ResourceTable.Id_mdtp_date_picker_month_and_day);
        mMonthAndDayView.setClickedListener(this);
        mSelectedMonthTextView = (Text) view.findComponentById(ResourceTable.Id_mdtp_date_picker_month);
        mSelectedDayTextView = (Text) view.findComponentById(ResourceTable.Id_mdtp_date_picker_day);
        mYearView = (Text) view.findComponentById(ResourceTable.Id_mdtp_date_picker_year);
        mYearView.setClickedListener(this);

        if (mVersion == Version.VERSION_2) {
            switch (calendarType) {
                case GREGORIAN:
                    mYearView.setTextAlignment(TextAlignment.LEFT);
                    mSelectedDayTextView.setTextAlignment(TextAlignment.LEFT);
                    break;
                case JALALI:
                    mYearView.setTextAlignment(TextAlignment.RIGHT);
                    mSelectedDayTextView.setTextAlignment(TextAlignment.RIGHT);
                    break;
            }
        }

        mDayPickerView = new DayPickerGroup(context, this, font);
        mYearPickerView = new YearPickerView(context, this, font);

        // if theme mode has not been set by java code, check if it is specified in Style.xml
        if (!mThemeDarkChanged) {
            mThemeDark = Utils.isDarkTheme(context, mThemeDark);
        }

        ResourceManager res = context.getResourceManager();
        mDayPickerDescription = ResourceUtils.getString(res, ResourceTable.String_mdtp_day_picker_description);
        mSelectDay = ResourceUtils.getString(res, ResourceTable.String_mdtp_select_day);
        mYearPickerDescription = ResourceUtils.getString(res, ResourceTable.String_mdtp_year_picker_description);
        mSelectYear = ResourceUtils.getString(res, ResourceTable.String_mdtp_select_year);

        int bgColorResource = mThemeDark ? ResourceTable.Color_mdtp_date_picker_view_animator_dark_theme : ResourceTable.Color_mdtp_date_picker_view_animator;
        ResourceUtils.setBackgroundColorRes(res, view, bgColorResource);

        mAnimator = (AccessibleDateAnimator) view.findComponentById(ResourceTable.Id_mdtp_animator);
        mAnimator.addComponent(mDayPickerView);
        mAnimator.addComponent(mYearPickerView);
        mAnimator.setDateMillis(mCalendar.getTimeInMillis());

        Button okButton = (Button) view.findComponentById(ResourceTable.Id_mdtp_ok);
        okButton.setClickedListener(v -> {
            tryVibrate();
            notifyOnDateListener();
            dismiss();
        });

        if (font == null) {
            try {
                okButton.setFont(Utils.getFont(context, "resources/rawfile/fonts/" + "robotomedium.ttf"));
            } catch (IOException e) {
                e.getMessage();
            }
        } else {
            okButton.setFont(font);
        }

        if (mOkString != null) okButton.setText(mOkString);
        else okButton.setText(mOkResid);

        Button cancelButton = (Button) view.findComponentById(ResourceTable.Id_mdtp_cancel);
        cancelButton.setClickedListener(v -> {
            tryVibrate();
            cancel();
        });
//        cancelButton.setFont(ResourcesCompat.getFont(activity, R.font.robotomedium));
        if (font == null) {
            try {
                cancelButton.setFont(Utils.getFont(context, "resources/rawfile/fonts/" + "robotomedium.ttf"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            cancelButton.setFont(font);
        }
        if (mCancelString != null) cancelButton.setText(mCancelString);
        else cancelButton.setText(mCancelResid);
        cancelButton.setVisibility(isCancelable() ? Component.VISIBLE : Component.HIDE);

        // If an accent color has not been set manually, get it from the context
        if (mAccentColor == null) {
//            mAccentColor = Utils.getAccentColorFromThemeIfAvailable(getFractionAbility());
            mAccentColor = 0xffff4081;
            HiLog.warn(LABEL, "createCompnent mAccentColor assigned:" + mAccentColor);
        } else {
            HiLog.warn(LABEL, "createCompnent mAccentColor already exist:" + mAccentColor);
        }
        if (mDatePickerHeaderView != null)
            ResourceUtils.setBackgroundColor(mDatePickerHeaderView, Utils.darkenColor(mAccentColor));
        if (String.valueOf(mAccentColor).endsWith("49023")) { // //TODO 以上的解析顏色和Demo不对应，暂用此方式替代
            ResourceUtils.setBackgroundColorRes(context.getResourceManager(), mDatePickerHeaderView, ResourceTable.Color_mdtp_calendar_demo_date_header);
        }
        ResourceUtils.setBackgroundColor(view.findComponentById(ResourceTable.Id_mdtp_day_picker_selected_date_layout), mAccentColor);

        // Buttons can have a different color
        if (mOkColor == null) {
            mOkColor = mAccentColor;
        }
        okButton.setTextColor(new Color(mOkColor));

        if (mCancelColor == null) {
            mCancelColor = mAccentColor;
        }
        cancelButton.setTextColor(new Color(mCancelColor));

        updateDisplay(false);
        setCurrentView(currentView);

        HiLog.warn(LABEL, "createCompnent listPosition:" + listPosition);
        if (listPosition != -1) {
            if (currentView == MONTH_AND_DAY_VIEW) {
                mDayPickerView.postSetSelection(listPosition);
            } else if (currentView == YEAR_VIEW) {
                mYearPickerView.postSetSelectionCentered(listPosition);
            }
        }
        setUiFont();
        return view;
    }

    private void setUiFont() {
        if (font == null) return;

        if (mDatePickerHeaderView != null) mDatePickerHeaderView.setFont(font);
        if (mSelectedMonthTextView != null) mSelectedMonthTextView.setFont(font);
        if (mSelectedDayTextView != null) mSelectedDayTextView.setFont(font);
        if (mYearView != null) mYearView.setFont(font);
    }


    @Override
    protected void onWindowConfigUpdated(WindowManager.LayoutConfig configParam) {
        super.onWindowConfigUpdated(configParam);
        Utils.log("DatePickerDialog onWindowConfigUpdated dialog rect:" + new RectFloat(configParam.x, configParam.y, configParam.x + configParam.width, configParam.y + configParam.height));
    }

    public void setCalendarType(Type calendarType) {
        this.calendarType = calendarType;
    }

    @Override
    public void onShow() {
        super.onShow();
        mHapticFeedbackController.start();
    }

    @Override
    public void onHide() {
        super.onHide();
        mHapticFeedbackController.stop();
        Utils.log("DatePickerDialog onHide");
        if (mDismissOnPause) dismiss();
    }

    private void setCurrentView(final int viewIndex) {
        HiLog.warn(LABEL, "### setCurrentView viewIndex:" + viewIndex);
        long millis = mCalendar.getTimeInMillis();

        switch (viewIndex) {
            case MONTH_AND_DAY_VIEW:
                if (mVersion == Version.VERSION_1) {
                    AnimatorValue pulseAnimator = Utils.getPulseAnimator(mMonthAndDayView, 0.9f,
                        1.05f);
                    if (mDelayAnimation) {
                        pulseAnimator.setDelay(ANIMATION_DELAY);
                        mDelayAnimation = false;
                    }
                    if (mCurrentView != viewIndex) {
                        mMonthAndDayView.setSelected(true);
                        mYearView.setSelected(false);
                        mAnimator.setCurrentIndex(MONTH_AND_DAY_VIEW);
                        mCurrentView = viewIndex;
                    }
                    HiLog.warn(LABEL, "### before mDayPickerView.onDateChanged()");
                    mDayPickerView.onDateChanged();
                    pulseAnimator.start();
                } else {
                    if (mCurrentView != viewIndex) {
                        mMonthAndDayView.setSelected(true);
                        mYearView.setSelected(false);
                        mAnimator.setCurrentIndex(MONTH_AND_DAY_VIEW);
                        mCurrentView = viewIndex;
                        if (calendarType == Type.JALALI) {
                            mMonthAndDayView.setAlignment(LayoutAlignment.RIGHT);
                        } else {
                            mMonthAndDayView.setAlignment(LayoutAlignment.LEFT);
                        }
                    }
                    mDayPickerView.onDateChanged();
                }

                String dayString = new SimpleDateFormat("MMM dd").format(new Date(millis));
                mAnimator.setComponentDescription(mDayPickerDescription + ": " + dayString);
                Utils.tryAccessibilityAnnounce(mAnimator, mSelectDay);
                break;
            case YEAR_VIEW:
                if (mVersion == Version.VERSION_1) {
                    AnimatorValue pulseAnimator = Utils.getPulseAnimator(mYearView, 0.85f, 1.1f);
                    if (mDelayAnimation) {
                        pulseAnimator.setDelay(ANIMATION_DELAY);
                        mDelayAnimation = false;
                    }
                    mYearPickerView.onDateChanged();
                    if (mCurrentView != viewIndex) {
                        mMonthAndDayView.setSelected(false);
                        mYearView.setSelected(true);
                        mAnimator.setCurrentIndex(YEAR_VIEW);
                        mCurrentView = viewIndex;
                    }
                    pulseAnimator.start();
                } else {
                    mYearPickerView.onDateChanged();
                    if (mCurrentView != viewIndex) {
                        mMonthAndDayView.setSelected(false);
                        mMonthAndDayView.setAlignment(LayoutAlignment.RIGHT);
                        mYearView.setSelected(true);
                        mAnimator.setCurrentIndex(YEAR_VIEW);
                        mCurrentView = viewIndex;
                    }
                }

                CharSequence yearString = YEAR_FORMAT.format(millis);
                mAnimator.setComponentDescription(mYearPickerDescription + ": " + yearString);
                Utils.tryAccessibilityAnnounce(mAnimator, mSelectYear);
                break;
        }
    }

    private void updateDisplay(boolean announce) {
        switch (calendarType) {
            case GREGORIAN:
                mYearView.setText(YEAR_FORMAT.format(mCalendar.getTime()));
                break;
            case JALALI:
                mYearView.setText(String.format("%d", mCalendar.get(Calendar.YEAR)));
                break;
        }
        if (mVersion == Version.VERSION_1) {
            if (mDatePickerHeaderView != null) {
                if (mTitle != null)
                    mDatePickerHeaderView.setText(mTitle.toUpperCase(mLocale));
                else {
                    switch (calendarType) {
                        case JALALI:
                            mDatePickerHeaderView.setText(JalaliCalendar.getWeekDayName((mCalendar).get(Calendar.DAY_OF_WEEK)));
                            break;
                        default:
                            mDatePickerHeaderView.setText(mCalendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG,
                                mLocale).toUpperCase(mLocale));
                            break;
                    }
                }
            }
            switch (calendarType) {
                case GREGORIAN:
                    mYearView.setText(YEAR_FORMAT.format(mCalendar.getTime()));
                    mSelectedMonthTextView.setText(MONTH_FORMAT.format(mCalendar.getTime()));
                    break;
                case JALALI:
                    mYearView.setText(String.format("%d", mCalendar.get(Calendar.YEAR)));
                    mSelectedMonthTextView.setText(((JalaliCalendar) mCalendar).getMonthName());
                    break;
            }
            mSelectedDayTextView.setText(String.format("%02d", mCalendar.get(Calendar.DAY_OF_MONTH)));
        }

        if (mVersion == Version.VERSION_2) {
            switch (calendarType) {
                case GREGORIAN:
                    mSelectedDayTextView.setText(VERSION_2_FORMAT.format(mCalendar.getTime()));
                    if (mTitle != null)
                        mDatePickerHeaderView.setText(mTitle.toUpperCase(mLocale));
                    else
                        mDatePickerHeaderView.setVisibility(Component.HIDE);
                    break;
                case JALALI:
                    JalaliCalendar cal = ((JalaliCalendar) mCalendar);
                    String dayMonthText = String.format("%s %d %s",
                        cal.getWeekDayName(), cal.get(Calendar.DAY_OF_MONTH), cal.getMonthName());
                    mSelectedDayTextView.setText(dayMonthText);
                    if (mTitle != null)
                        mDatePickerHeaderView.setText(mTitle);
                    else
                        mDatePickerHeaderView.setVisibility(Component.HIDE);
                    break;
            }
        }

        // Accessibility.
        long millis = mCalendar.getTimeInMillis();
        mAnimator.setDateMillis(millis);
        String monthAndDayText = new SimpleDateFormat("MMM dd").format(new Date(millis));
        mMonthAndDayView.setComponentDescription(monthAndDayText);

        if (announce) {
            String fullDateText = new SimpleDateFormat("yyyy MMM dd").format(new Date(millis));
            Utils.tryAccessibilityAnnounce(mAnimator, fullDateText);
        }
    }

    /**
     * Set whether the device should vibrate when touching fields
     *
     * @param vibrate true if the device should vibrate when touching a field
     */
    public void vibrate(boolean vibrate) {
        mVibrate = vibrate;
    }

    /**
     * Set whether the picker should dismiss itself when being paused or whether it should try to survive an orientation change
     *
     * @param dismissOnPause true if the dialog should dismiss itself when it's pausing
     */
    public void dismissOnPause(boolean dismissOnPause) {
        mDismissOnPause = dismissOnPause;
    }

    /**
     * Set whether the picker should dismiss itself when a day is selected
     *
     * @param autoDismiss true if the dialog should dismiss itself when a day is selected
     */
    @SuppressWarnings("unused")
    public void autoDismiss(boolean autoDismiss) {
        mAutoDismiss = autoDismiss;
    }

    /**
     * Set whether the dark theme should be used
     *
     * @param themeDark true if the dark theme should be used, false if the default theme should be used
     */
    public void setThemeDark(boolean themeDark) {
        mThemeDark = themeDark;
        mThemeDarkChanged = true;
    }

    /**
     * Returns true when the dark theme should be used
     *
     * @return true if the dark theme should be used, false if the default theme should be used
     */
    @Override
    public boolean isThemeDark() {
        return mThemeDark;
    }

    /**
     * Set the accent color of this dialog
     *
     * @param color the accent color you want
     */
    @SuppressWarnings("unused")
    public void setAccentColor(String color) {
        mAccentColor = Color.getIntColor(color);
    }

    /**
     * Set the accent color of this dialog
     *
     * @param color the accent color you want
     */
    public void setAccentColor(int color) {
        RgbColor rgbColor = RgbColor.fromArgbInt(color);
        rgbColor.setAlpha(255);
        mAccentColor = rgbColor.asArgbInt();
        Utils.log(String.format("DatePickerDialog setAccentColor:%x", mAccentColor));
    }

    /**
     * Set the text color of the OK button
     *
     * @param color the color you want
     */
    @SuppressWarnings("unused")
    public void setOkColor(String color) {
        mOkColor = Color.getIntColor(color);
    }

    /**
     * Set the text color of the OK button
     *
     * @param color the color you want
     */
    @SuppressWarnings("unused")
    public void setOkColor(int color) {
        RgbColor rgbColor = RgbColor.fromArgbInt(color);
        rgbColor.setAlpha(255);
        mOkColor = rgbColor.asArgbInt();
    }

    /**
     * Set the text color of the Cancel button
     *
     * @param color the color you want
     */
    @SuppressWarnings("unused")
    public void setCancelColor(String color) {
        mCancelColor = Color.getIntColor(color);
    }

    /**
     * Set the text color of the Cancel button
     *
     * @param color the color you want
     */
    @SuppressWarnings("unused")
    public void setCancelColor(int color) {
        RgbColor rgbColor = RgbColor.fromArgbInt(color);
        rgbColor.setAlpha(255);
        mCancelColor = rgbColor.asArgbInt();
    }

    /**
     * Get the accent color of this dialog
     *
     * @return accent color
     */
    @Override
    public int getAccentColor() {
        return mAccentColor;
    }

    /**
     * Set whether the year picker of the month and day picker is shown first
     *
     * @param yearPicker boolean
     */
    public void showYearPickerFirst(boolean yearPicker) {
        mDefaultView = yearPicker ? YEAR_VIEW : MONTH_AND_DAY_VIEW;
    }

    @SuppressWarnings("unused")
    public void setFirstDayOfWeek(int startOfWeek) {
        if (startOfWeek < Calendar.SUNDAY || startOfWeek > Calendar.SATURDAY) {
            throw new IllegalArgumentException("Value must be between Calendar.SUNDAY and " +
                "Calendar.SATURDAY");
        }
        mWeekStart = startOfWeek;
        if (mDayPickerView != null) {
            mDayPickerView.onChange();
        }
    }

    @SuppressWarnings("unused")
    public void setYearRange(int startYear, int endYear) {
        mDefaultLimiter.setYearRange(startYear, endYear);

        if (mDayPickerView != null) {
            mDayPickerView.onChange();
        }
    }

    /**
     * Sets the minimal date supported by this DatePicker. Dates before (but not including) the
     * specified date will be disallowed from being selected.
     *
     * @param calendar a Calendar object set to the year, month, day desired as the mindate.
     */
    @SuppressWarnings("unused")
    public void setMinDate(Calendar calendar) {
        mDefaultLimiter.setMinDate(calendar);

        if (mDayPickerView != null) {
            mDayPickerView.onChange();
        }
    }

    /**
     * 获取最小日期
     *
     * @return The minimal date supported by this DatePicker. Null if it has not been set.
     */
    @SuppressWarnings("unused")
    public Calendar getMinDate() {
        return mDefaultLimiter.getMinDate();
    }

    /**
     * Sets the minimal date supported by this DatePicker. Dates after (but not including) the
     * specified date will be disallowed from being selected.
     *
     * @param calendar a Calendar object set to the year, month, day desired as the maxdate.
     */
    @SuppressWarnings("unused")
    public void setMaxDate(Calendar calendar) {
        mDefaultLimiter.setMaxDate(calendar);

        if (mDayPickerView != null) {
            mDayPickerView.onChange();
        }
    }

    /**
     * 获取最大日期
     *
     * @return The maximal date supported by this DatePicker. Null if it has not been set.
     */
    @SuppressWarnings("unused")
    public Calendar getMaxDate() {
        return mDefaultLimiter.getMaxDate();
    }

    /**
     * Sets an array of dates which should be highlighted when the picker is drawn
     *
     * @param highlightedDays an Array of Calendar objects containing the dates to be highlighted
     */
    @SuppressWarnings("unused")
    public void setHighlightedDays(Calendar[] highlightedDays) {
        for (Calendar highlightedDay : highlightedDays) {
            this.highlightedDays.add(Utils.trimToMidnight((Calendar) highlightedDay.clone()));
        }
        if (mDayPickerView != null) mDayPickerView.onChange();
    }

    /**
     * 突出显示的日历
     *
     * @return The list of dates, as Calendar Objects, which should be highlighted. null is no dates should be highlighted
     */
    @SuppressWarnings("unused")
    public Calendar[] getHighlightedDays() {
        if (highlightedDays.isEmpty()) return null;
        Calendar[] output = highlightedDays.toArray(new Calendar[0]);
        Arrays.sort(output);
        return output;
    }

    @Override
    public boolean isHighlighted(int year, int month, int day) {
        Calendar date = Calendar.getInstance(getTimeZone());
        date.set(Calendar.YEAR, year);
        date.set(Calendar.MONTH, month);
        date.set(Calendar.DAY_OF_MONTH, day);
        Utils.trimToMidnight(date);
        return highlightedDays.contains(date);
    }

    /**
     * Sets a list of days which are the only valid selections.
     * Setting this value will take precedence over using setMinDate() and setMaxDate()
     *
     * @param selectableDays an Array of Calendar Objects containing the selectable dates
     */
    @SuppressWarnings("unused")
    public void setSelectableDays(Calendar[] selectableDays) {
        mDefaultLimiter.setSelectableDays(selectableDays);
        if (mDayPickerView != null) mDayPickerView.onChange();
    }

    /**
     * 选择的天
     *
     * @return an Array of Calendar objects containing the list with selectable items. null if no restriction is set
     */
    @SuppressWarnings("unused")
    public Calendar[] getSelectableDays() {
        return mDefaultLimiter.getSelectableDays();
    }

    /**
     * Sets a list of days that are not selectable in the picker
     * Setting this value will take precedence over using setMinDate() and setMaxDate(), but stacks with setSelectableDays()
     *
     * @param disabledDays an Array of Calendar Objects containing the disabled dates
     */
    @SuppressWarnings("unused")
    public void setDisabledDays(Calendar[] disabledDays) {
        mDefaultLimiter.setDisabledDays(disabledDays);
        if (mDayPickerView != null) mDayPickerView.onChange();
    }

    /**
     * getDisabledDays
     *
     * @return an Array of Calendar objects containing the list of days that are not selectable. null if no restriction is set
     */
    @SuppressWarnings("unused")
    public Calendar[] getDisabledDays() {
        return mDefaultLimiter.getDisabledDays();
    }

    /**
     * Provide a DateRangeLimiter for full control over which dates are enabled and disabled in the picker
     *
     * @param dateRangeLimiter An implementation of the DateRangeLimiter interface
     */
    @SuppressWarnings("unused")
    public void setDateRangeLimiter(DateRangeLimiter dateRangeLimiter) {
        mDateRangeLimiter = dateRangeLimiter;
    }

    /**
     * Set a title to be displayed instead of the weekday
     *
     * @param title String - The title to be displayed
     */
    public void setTitle(String title) {
        mTitle = title;
    }

    /**
     * Set the label for the Ok button (max 12 characters)
     *
     * @param okString A literal String to be used as the Ok button label
     */
    @SuppressWarnings("unused")
    public void setOkText(String okString) {
        mOkString = okString;
    }

    /**
     * Set the label for the Ok button (max 12 characters)
     *
     * @param okResid A resource ID to be used as the Ok button label
     */
    @SuppressWarnings("unused")
    public void setOkText(/*@StringRes*/ int okResid) {
        mOkString = null;
        mOkResid = okResid;
    }

    /**
     * Set the label for the Cancel button (max 12 characters)
     *
     * @param cancelString A literal String to be used as the Cancel button label
     */
    @SuppressWarnings("unused")
    public void setCancelText(String cancelString) {
        mCancelString = cancelString;
    }

    /**
     * Set the label for the Cancel button (max 12 characters)
     *
     * @param cancelResid A resource ID to be used as the Cancel button label
     */
    @SuppressWarnings("unused")
    public void setCancelText(/*@StringRes*/ int cancelResid) {
        mCancelString = null;
        mCancelResid = cancelResid;
    }

    /**
     * Set which layout version the picker should use
     *
     * @param version The version to use
     */
    public void setVersion(Version version) {
        mVersion = version;
    }

    /**
     * Get the layout version the Dialog is using
     *
     * @return Version
     */
    public Version getVersion() {
        return mVersion;
    }

    /**
     * Set which way the user needs to swipe to switch months in the MonthView
     *
     * @param orientation The orientation to use
     */
    public void setScrollOrientation(ScrollOrientation orientation) {
        mScrollOrientation = orientation;
    }

    /**
     * Get which way the user needs to swipe to switch months in the MonthView
     *
     * @return SwipeOrientation
     */
    public ScrollOrientation getScrollOrientation() {
        return mScrollOrientation;
    }

    @Override
    public Type getCalendarType() {
        return calendarType;
    }

    /**
     * Set which timezone the picker should use
     * <p>
     * This has been deprecated in favor of setting the TimeZone using the constructor that
     * takes a Calendar object
     *
     * @param timeZone The timezone to use
     */
    @SuppressWarnings("DeprecatedIsStillUsed")
    @Deprecated
    public void setTimeZone(TimeZone timeZone) {
        mTimezone = timeZone;
        mCalendar.setTimeZone(timeZone);
        YEAR_FORMAT.setTimeZone(timeZone);
        MONTH_FORMAT.setTimeZone(timeZone);
        DAY_FORMAT.setTimeZone(timeZone);
    }

    /**
     * Set a custom locale to be used when generating various strings in the picker
     *
     * @param locale Locale
     */
    @SuppressWarnings("WeakerAccess")
    public void setLocale(Locale locale) {
        mLocale = locale;
        mWeekStart = Calendar.getInstance(mTimezone, mLocale).getFirstDayOfWeek();
        YEAR_FORMAT = new SimpleDateFormat("yyyy", locale);
        MONTH_FORMAT = new SimpleDateFormat("MMM", locale);
        DAY_FORMAT = new SimpleDateFormat("dd", locale);
    }

    /**
     * Return the current locale (default or other)
     *
     * @return Locale
     */
    @Override
    public Locale getLocale() {
        return mLocale;
    }

    @SuppressWarnings("unused")
    public void setOnDateSetListener(OnDateSetListener listener) {
        mCallBack = listener;
    }

    /**
     * Get a reference to the callback
     *
     * @return OnDateSetListener the callback
     */
    @SuppressWarnings("unused")
    public OnDateSetListener getOnDateSetListener() {
        return mCallBack;
    }

    // If the newly selected month / year does not contain the currently selected day number,
    // change the selected day number to the last day of the selected month or year.
    //      e.g. Switching from Mar to Apr when Mar 31 is selected -> Apr 30
    //      e.g. Switching from 2012 to 2013 when Feb 29, 2012 is selected -> Feb 28, 2013
    private Calendar adjustDayInMonthIfNeeded(Calendar calendar) {
        int day = calendar.get(Calendar.DAY_OF_MONTH);
        int daysInMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
        if (day > daysInMonth) {
            calendar.set(Calendar.DAY_OF_MONTH, daysInMonth);
        }
        return mDateRangeLimiter.setToNearestDate(calendar);
    }

    @Override
    public void onClick(Component c) {
        tryVibrate();
        if (c.getId() == ResourceTable.Id_mdtp_date_picker_year) {
            setCurrentView(YEAR_VIEW);
        } else if (c.getId() == ResourceTable.Id_mdtp_date_picker_month_and_day) {
            // TODO 过滤横向日历点击抖动动画
            if (mVersion == Version.VERSION_1) {
                setCurrentView(MONTH_AND_DAY_VIEW);
            }
        }
    }

    @Override
    public void onYearSelected(int year) {
        mCalendar.set(Calendar.YEAR, year);
        mCalendar = adjustDayInMonthIfNeeded(mCalendar);
        updatePickers();
        setCurrentView(MONTH_AND_DAY_VIEW);
        updateDisplay(true);
    }

    @Override
    public void onDayOfMonthSelected(int year, int month, int day) {
        Utils.log("DatePickerDialog onDayOfMonthSelected year:" + year + ", month:" + month + ", day:" + day);
        mCalendar.set(Calendar.YEAR, year);
        mCalendar.set(Calendar.MONTH, month);
        mCalendar.set(Calendar.DAY_OF_MONTH, day);
        updatePickers();
        updateDisplay(true);
        if (mAutoDismiss) {
            notifyOnDateListener();
            dismiss();
        }
    }

    private void updatePickers() {
        for (OnDateChangedListener listener : mListeners) listener.onDateChanged();
    }


    @Override
    public MonthAdapter.CalendarDay getSelectedDay() {
        return new MonthAdapter.CalendarDay(mCalendar, getTimeZone());
    }

    @Override
    public Calendar getStartDate() {
        return mDateRangeLimiter.getStartDate();
    }

    @Override
    public Calendar getEndDate() {
        return mDateRangeLimiter.getEndDate();
    }

    @Override
    public int getMinYear() {
        return mDateRangeLimiter.getMinYear();
    }

    @Override
    public int getMaxYear() {
        return mDateRangeLimiter.getMaxYear();
    }


    @Override
    public boolean isOutOfRange(int year, int month, int day) {
        return mDateRangeLimiter.isOutOfRange(year, month, day);
    }

    @Override
    public int getFirstDayOfWeek() {
        return mWeekStart;
    }

    @Override
    public void registerOnDateChangedListener(OnDateChangedListener listener) {
        mListeners.add(listener);
    }

    @Override
    public void unregisterOnDateChangedListener(OnDateChangedListener listener) {
        mListeners.remove(listener);
    }

    @Override
    public void tryVibrate() {
        if (mVibrate) mHapticFeedbackController.tryVibrate();
    }

    @Override
    public TimeZone getTimeZone() {
        return mTimezone == null ? TimeZone.getDefault() : mTimezone;
    }

    public void notifyOnDateListener() {
        if (mCallBack != null) {
            mCallBack.onDateSet(DatePickerDialog.this, mCalendar.get(Calendar.YEAR),
                mCalendar.get(Calendar.MONTH), mCalendar.get(Calendar.DAY_OF_MONTH));
        }
    }

    @Override
    public void onBackground(DialogInterface dialogInterface) {
        if (mDismissOnPause) {
            dismiss();
        }
    }

    public void setFont(Font font) {
        this.font = font;
    }
}
